using System;

namespace Tycho.Parser.Peg {
    public class MultipleRule : Rule {
        private readonly int Maximum;
        private readonly int Minimum;
        private readonly IRule Rule;

        public MultipleRule (int minimum, int maximum, IRule rule) {
            Minimum = minimum;
            Maximum = maximum;
            Rule = rule;
        }

        public override Yield Parse (char[] source, int index, ParseContext context, string sourceString, ParseEnvironment environment, Func<RuleParseResult, Yield> continuation) {
            return Rule.Parse (source, index, context, sourceString, environment, lastResult => {
                if (lastResult != null) {
                    return ParseNextItem (lastResult, 1, source, sourceString, environment, continuation);
                } else if (0 >= Minimum) {
                    return () => continuation (new RuleParseResult (index, context));
                } else {
                    return () => continuation (null);
                }
            });
        }

        private Yield ParseNextItem (RuleParseResult lastResult, int matches, char[] source, string sourceString, ParseEnvironment environment, Func<RuleParseResult, Yield> continuation) {
            if (LessThanMaximum (matches)) {
                return Rule.Parse (source, lastResult.Index, lastResult.Context, sourceString, environment, speculativeResult => {
                    if (speculativeResult == null) {
                        if (matches >= Minimum) {
                            return () => continuation (lastResult);
                        } else {
                            return () => continuation (null);
                        }
                    } else {
                        speculativeResult.MergeCapturesFrom (lastResult);
                        return () => ParseNextItem (speculativeResult, matches + 1, source, sourceString, environment, continuation);
                    }
                });
            } else {
                return () => continuation (lastResult);
            }
        }

        private bool LessThanMaximum (int matches) {
            if (Maximum == -1) {
                return true;
            } else {
                return matches < Maximum;
            }
        }
    }
}